package com.wdb.common.plugin;

import lombok.extern.slf4j.Slf4j;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.CommentGeneratorConfiguration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.internal.util.StringUtility;
import tk.mybatis.mapper.MapperException;
import tk.mybatis.mapper.generator.MapperCommentGenerator;

import java.util.*;

@Slf4j
public class FluentMapperPlugin extends PluginAdapter {
    private Set<String> mappers = new HashSet<>();
    private boolean caseSensitive = false;
    //开始的分隔符，例如mysql为`，sqlserver为[
    private String beginningDelimiter = "";
    //结束的分隔符，例如mysql为`，sqlserver为]
    private String endingDelimiter = "";
    //数据库模式
    private String schema;
    //注释生成器
    private CommentGeneratorConfiguration commentCfg;
    //强制生成注解
    private boolean forceAnnotation;

    @Override
    public void setContext(Context context) {
        super.setContext(context);
        //设置默认的注释生成器
        commentCfg = new CommentGeneratorConfiguration();
        commentCfg.setConfigurationType(MapperCommentGenerator.class.getCanonicalName());
        context.setCommentGeneratorConfiguration(commentCfg);
        //支持oracle获取注释#114
        context.getJdbcConnectionConfiguration().addProperty("remarksReporting", "true");
    }

    @Override
    public void setProperties(Properties properties) {
        super.setProperties(properties);
        String mappers = this.properties.getProperty("mappers");
        if (StringUtility.stringHasValue(mappers)) {
            this.mappers.addAll(Arrays.asList(mappers.split(",")));
        } else {
            throw new MapperException("Mapper插件缺少必要的mappers属性!");
        }
        String caseSensitive = this.properties.getProperty("caseSensitive");
        if (StringUtility.stringHasValue(caseSensitive)) {
            this.caseSensitive = caseSensitive.equalsIgnoreCase("TRUE");
        }
        String forceAnnotation = this.properties.getProperty("forceAnnotation");
        if (StringUtility.stringHasValue(forceAnnotation)) {
            commentCfg.addProperty("forceAnnotation", forceAnnotation);
            this.forceAnnotation = forceAnnotation.equalsIgnoreCase("TRUE");
        }
        String beginningDelimiter = this.properties.getProperty("beginningDelimiter");
        if (StringUtility.stringHasValue(beginningDelimiter)) {
            this.beginningDelimiter = beginningDelimiter;
        }
        commentCfg.addProperty("beginningDelimiter", this.beginningDelimiter);
        String endingDelimiter = this.properties.getProperty("endingDelimiter");
        if (StringUtility.stringHasValue(endingDelimiter)) {
            this.endingDelimiter = endingDelimiter;
        }
        commentCfg.addProperty("endingDelimiter", this.endingDelimiter);
        String schema = this.properties.getProperty("schema");
        if (StringUtility.stringHasValue(schema)) {
            this.schema = schema;
        }
    }

    public String getDelimiterName(String name) {
        StringBuilder nameBuilder = new StringBuilder();
        if (StringUtility.stringHasValue(schema)) {
            nameBuilder.append(schema);
            nameBuilder.append(".");
        }
        nameBuilder.append(beginningDelimiter);
        nameBuilder.append(name);
        nameBuilder.append(endingDelimiter);
        return nameBuilder.toString();
    }

    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    /**
     * 生成的Mapper接口
     */
    @Override
    public boolean clientGenerated(Interface ifz, TopLevelClass clz, IntrospectedTable table) {
        //获取实体类
        FullyQualifiedJavaType entityType = new FullyQualifiedJavaType(table.getBaseRecordType());
        //import接口
        for (String mapper : mappers) {
            ifz.addImportedType(new FullyQualifiedJavaType(mapper));
            ifz.addSuperInterface(new FullyQualifiedJavaType(mapper + "<" + entityType.getShortName() + ">"));
        }
        //import实体类
        ifz.addImportedType(entityType);
        return true;
    }

    /**
     * 处理实体类的包和@Table注解
     */
    private void processEntityClass(TopLevelClass clz, IntrospectedTable introspectedTable) {
        // 添加序列化ID

        Field field = new Field();

        field.setFinal(true);
        field.setStatic(true);
        field.setName("serialVersionUID");
        field.setInitializationString("1L");
        field.setVisibility(JavaVisibility.PRIVATE);
        field.setType(new FullyQualifiedJavaType("long"));

        clz.addField(field);

        // 添加 build 方法

        Method build = new Method();

        build.setStatic(true);
        build.setName("build");
        build.setReturnType(clz.getType());
        build.setVisibility(JavaVisibility.PUBLIC);
        build.addBodyLine("return new " + clz.getType().getShortName() + "();");

        clz.addMethod(build);

        // 添加 id 方法

        Method id = new Method();

        id.setName("id");
        id.setReturnType(clz.getType());
        id.setVisibility(JavaVisibility.PUBLIC);

        id.addBodyLine("this.setId(" + id.getName() + ");");
        id.addBodyLine("return this;");

        id.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.lang.Long"),id.getName())));

        clz.addMethod(id);

        // 添加 created 方法

        Method created = new Method();

        created.setName("created");
        created.setReturnType(clz.getType());
        created.setVisibility(JavaVisibility.PUBLIC);

        created.addBodyLine("this.setCreated(" + created.getName() + ");");
        created.addBodyLine("return this;");

        created.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.util.Date"),created.getName())));

        clz.addMethod(created);


        // 添加 modified 方法

        Method modified = new Method();

        modified.setName("modified");
        modified.setReturnType(clz.getType());
        modified.setVisibility(JavaVisibility.PUBLIC);

        modified.addBodyLine("this.setModified(" + modified.getName() + ");");
        modified.addBodyLine("return this;");

        modified.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.util.Date"),modified.getName())));

        clz.addMethod(modified);

        // 添加 deleted 方法

        Method deleted = new Method();

        deleted.setName("deleted");
        deleted.setReturnType(clz.getType());
        deleted.setVisibility(JavaVisibility.PUBLIC);

        deleted.addBodyLine("this.setDeleted(" + deleted.getName() + ");");
        deleted.addBodyLine("return this;");

        deleted.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.lang.String"),deleted.getName())));

        clz.addMethod(deleted);

        // 添加 createUserId 方法

        Method createUserId = new Method();

        createUserId.setName("createUserId");
        createUserId.setReturnType(clz.getType());
        createUserId.setVisibility(JavaVisibility.PUBLIC);

        createUserId.addBodyLine("this.setCreateUserId(" + createUserId.getName() + ");");
        createUserId.addBodyLine("return this;");

        createUserId.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.lang.Long"),createUserId.getName())));

        clz.addMethod(createUserId);

        // 添加 updateUserId 方法

        Method updateUserId = new Method();

        updateUserId.setName("updateUserId");
        updateUserId.setReturnType(clz.getType());
        updateUserId.setVisibility(JavaVisibility.PUBLIC);

        updateUserId.addBodyLine("this.setUpdateUserId(" + updateUserId.getName() + ");");
        updateUserId.addBodyLine("return this;");

        updateUserId.getParameters().addAll(Collections.singleton(new Parameter(new FullyQualifiedJavaType("java.lang.Long"),updateUserId.getName())));

        clz.addMethod(updateUserId);

        // 导包

        clz.addImportedType("javax.persistence.*");

        clz.addImportedType("lombok.Getter");
        clz.addImportedType("lombok.Setter");
        clz.addImportedType("java.util.Date");
        clz.addImportedType("lombok.ToString");


        clz.addAnnotation("@Getter");
        clz.addAnnotation("@Setter");
        clz.addAnnotation("@ToString(callSuper = true)");

        String tableName = introspectedTable.getFullyQualifiedTableNameAtRuntime();
        //如果包含空格，或者需要分隔符，需要完善
        if (StringUtility.stringContainsSpace(tableName)) {
            tableName = context.getBeginningDelimiter()
                    + tableName
                    + context.getEndingDelimiter();
        }
        //是否忽略大小写，对于区分大小写的数据库，会有用
        if (caseSensitive && !clz.getType().getShortName().equals(tableName)) {
            clz.addAnnotation("@Table(name = \"" + getDelimiterName(tableName) + "\")");
        } else if (!clz.getType().getShortName().equalsIgnoreCase(tableName)) {
            clz.addAnnotation("@Table(name = \"" + getDelimiterName(tableName) + "\")");
        } else if (StringUtility.stringHasValue(schema)
                || StringUtility.stringHasValue(beginningDelimiter)
                || StringUtility.stringHasValue(endingDelimiter)) {
            clz.addAnnotation("@Table(name = \"" + getDelimiterName(tableName) + "\")");
        } else if (forceAnnotation) {
            clz.addAnnotation("@Table(name = \"" + getDelimiterName(tableName) + "\")");
        }
    }

    @Override
    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        processEntityClass(topLevelClass, introspectedTable);
        return true;
    }

    @Override
    public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        processEntityClass(topLevelClass, introspectedTable);
        return true;
    }

    @Override
    public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        processEntityClass(topLevelClass, introspectedTable);
        return false;
    }

    //下面所有return false的方法都不生成。这些都是基础的CRUD方法，使用通用Mapper实现
    @Override
    public boolean clientDeleteByPrimaryKeyMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientInsertMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientInsertSelectiveMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientSelectByPrimaryKeyMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeySelectiveMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithBLOBsMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithoutBLOBsMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientDeleteByPrimaryKeyMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientInsertMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientInsertSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientSelectAllMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientSelectAllMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientSelectByPrimaryKeyMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeySelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithoutBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapDeleteByPrimaryKeyElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapInsertElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapInsertSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapSelectAllElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapSelectByPrimaryKeyElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeySelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean providerGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean providerApplyWhereMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean providerInsertSelectiveMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    @Override
    public boolean providerUpdateByPrimaryKeySelectiveMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        return false;
    }

    /**
     * 不生成GETTER 使用LOMBOK自动生成
     */
    @Override
    public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        return false;
    }

    /**
     * 不生成SETTER 使用LOMBOK自动生成, 只生成FLUENT风格的代码
     */
    @Override
    public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        Method fluentMethod = new Method();
        fluentMethod.setVisibility(JavaVisibility.PUBLIC);
        fluentMethod.setReturnType(topLevelClass.getType());
        fluentMethod.setName(method.getName().substring(3,4).toLowerCase()+method.getName().substring(4)); //$NON-NLS-1$
        fluentMethod.getParameters().addAll(method.getParameters());


        context.getCommentGenerator().addGeneralMethodComment(fluentMethod,
                introspectedTable);
        StringBuilder sb = new StringBuilder()
                .append("this.")
                .append(method.getName())
                .append("(")
                .append(introspectedColumn.getJavaProperty())
                .append(");");
        fluentMethod.addBodyLine(sb.toString()); //$NON-NLS-1$
        fluentMethod.addBodyLine("return this;");

        topLevelClass.addMethod(fluentMethod);

        return false;
    }
}
